//
//  FreebaseSuggestSearch.m
//  Freebase
//
//  Created by Alec Flett on 12/12/09.
//  Copyright 2009 __MyCompanyName__. All rights reserved.
//

#import "FreebaseSuggestSearch.h"
#import "FBTableViewCell.h"
#import "JSON/JSON.h"

static float DISTANCES[] = {
0, // all
3.0,
1.0,
0.5,
0.25

};


@implementation FreebaseSuggestSearch
@synthesize allowCreateNew = _allowCreateNew;
@synthesize currentResult = _currentResult;
@synthesize currentRequest = _currentRequest;
@synthesize activityView = _activityView;
@synthesize activityLabel = _activityLabel;
@synthesize allowGeolocation = _allowGeolocation;
@synthesize currentLocation = _currentLocation;
@synthesize preferredType = _preferredType;
@synthesize strictType = _strictType;
@synthesize currentItem = _currentItem;
@synthesize suggestDelegate = _suggestDelegate;
@synthesize searchScopeIndex = _searchScopeIndex;
@synthesize searchString = _searchString;
@dynamic mqlFilter, mqlOutput;

- (id)initWithStyle:(UITableViewStyle)style {
    // Override initWithStyle: if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
    if ((self = [super initWithStyle:style])) {
        NSLog(@"initWithStyle: %@", self);
        self.preferredType = @"/common/topic";
        self.allowGeolocation = YES;

        _locationManager = [[CLLocationManager alloc] init];
        _locationManager.delegate = self;
    }
    return self;
}


/*
- (void)viewDidLoad {
    [super viewDidLoad];

    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
    // self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
*/
/*
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
}
*/
/*
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
}
*/
/*
- (void)viewWillDisappear:(BOOL)animated {
	[super viewWillDisappear:animated];
}
*/
/*
- (void)viewDidDisappear:(BOOL)animated {
	[super viewDidDisappear:animated];
}
*/

/*
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/

- (void)didReceiveMemoryWarning {
	// Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
	
	// Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
	// Release any retained subviews of the main view.
	// e.g. self.myOutlet = nil;
    NSLog(@"View unloaded...");
}

- (void)setMqlFilter:(NSString*)mqlFilter
{
    [_mqlFilter release];
    
    if (![mqlFilter isKindOfClass:[NSString class]])
        mqlFilter = [mqlFilter JSONRepresentation];
    
    _mqlFilter = [mqlFilter retain];
}

- (NSString*)mqlFilter
{
    return _mqlFilter;
}

- (void)setMqlOutput:(NSString*)mqlOutput
{
    [_mqlOutput release];
    
    if (![mqlOutput isKindOfClass:[NSString class]])
        mqlOutput = [mqlOutput JSONRepresentation];
    
    _mqlOutput = [mqlOutput retain];
}

- (NSString*)mqlOutput
{
    return _mqlOutput;
}


- (void)searchFinished:(id)result
{
    NSLog(@"Search complete, %d items, reloading %@", [result count], 
          _searchResultsTableView);
    self.currentResult = result;
    _activityView.hidden = YES;
    [_searchResultsTableView reloadData];
}

- (void)geoSearchFinished:(NSDictionary*)result
{
    self.currentResult = [result valueForKeyPath:@"features.properties"];
    NSLog(@"Geosearch complete, %d items", [self.currentResult count]);
    _activityView.hidden = YES;
    [_searchResultsTableView reloadData];
}


- (void)maybeBeginSearch:(NSString*)text scopeIndex:(NSInteger)scopeIndex
{
    // cancel any existing timer
    if (_searchTimer) {
        NSLog(@"Cancelling existing timer");
        [_searchTimer invalidate];
        [_searchTimer release];
    }
    
    self.searchString = [text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
    self.searchScopeIndex = scopeIndex;
    
    NSLog(@"Setting a timer...");
    // set a new one
    _searchTimer = [[NSTimer scheduledTimerWithTimeInterval:0.3 
                                                     target:self 
                                                   selector:@selector(beginTimerSearch:)
                                                   userInfo:nil
                                                    repeats:NO] retain];    
}

- (void)beginTimerSearch:(NSTimer*)timer 
{
    if (timer == _searchTimer) {

        [self beginSearch];
        
        [_searchTimer invalidate];
        [_searchTimer release];
        _searchTimer = nil;
    }
}


- (void)beginSearch
{    
    NSLog(@"Begin search for %@ with scope %d", _searchString,
          _allowGeolocation && _searchScopeIndex);
    
    NSInteger scopeIndex = _allowGeolocation ? _searchScopeIndex: 0;
    
    if (scopeIndex == 0) {
        
        if (_searchString && [_searchString length]) {
            NSString* searchString = _searchString;
            NSLog(@"Showing activity view: %@", _activityView);
            _activityView.hidden = NO;
            if ([searchString length]>1 && 
                ![searchString hasSuffix:@"*"])
                searchString = [NSString stringWithFormat:@"%@*", searchString];
            [self.currentRequest cancel];            
            NSMutableDictionary* options = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                            _preferredType, @"type",
                                            nil];
            [self updateSearchOptions:options];
            
            self.currentRequest = [[FreebaseSession session] search:searchString 
                                                               name:@"suggest" 
                                                           delegate:self 
                                                  didFinishSelector:@selector(searchFinished:)
                                                            options:options];            
        }
        else
            NSLog(@"No search string: %@", _searchString);
    }
    else {
        [self beginGeosearch];
    }
}


- (void)beginGeosearch {
    NSLog(@"beginGeosearch");
    _activityLabel.text = @"Locating...";
    _activityView.hidden = NO;
    
    // this is what I want to do:
    // _searchResultsTableView.hidden = NO;
    if (!_awaitingLocation) {
        NSLog(@"Awaiting location, so queing up a search");
        _awaitingLocationForSearch = YES;
        _awaitingLocation = YES;
        [_locationManager startUpdatingLocation];
    } else if (!_currentLocation) {
        NSLog(@"Not awaiting, but still missing _currentLocation");
        // we're awaiting a location, but haven't yet started searching
        _awaitingLocationForSearch = YES;
    } else {
        NSLog(@"Kicking off a geosearch...");
        _activityLabel.text = @"Loading...";
        _awaitingLocationForSearch = NO;
        // ok, we have a location, lets search!
        CLLocationDegrees latitude = _currentLocation.coordinate.latitude;
        CLLocationDegrees longtitude = _currentLocation.coordinate.longitude;
        NSDictionary* geoJsonLocation = [NSDictionary dictionaryWithObjectsAndKeys:
                                         @"Point", @"type",
                                         [NSArray arrayWithObjects:
                                          [NSNumber numberWithDouble:longtitude],
                                          [NSNumber numberWithDouble:latitude], nil],
                                         @"coordinates",
                                         nil];
        NSString* locationString = [geoJsonLocation JSONRepresentation];
        
        NSArray *mql_output = [NSArray arrayWithObject:
                               [NSDictionary dictionaryWithObjectsAndKeys:
                                [NSNull null], @"id",
                                [NSNull null], @"name",
                                [NSArray arrayWithObject:
                                 [NSDictionary dictionaryWithObjectsAndKeys:
                                  [NSNull null], @"id",
                                  [NSNull null], @"name",
                                  [NSNumber numberWithBool:YES], @"optional", nil]], @"type",
                                nil]];
        
        NSString *mql_output_string = [[mql_output JSONRepresentation] retain];
        //[{"type": {"optional": "forbidden",
        //          "id|=": ["/location/country", "/location/us_state", "/location/continent", "/location/us_county"]}}]
        
        NSString* distance = [NSString stringWithFormat:@"%f", 
                              DISTANCES[_searchScopeIndex]];
        
        NSString *searchString = _searchString;
        
        if (searchString && [searchString length]) {
            NSString *geo_filter = [[NSDictionary dictionaryWithObjectsAndKeys:
                                     distance, @"within",
                                     locationString, @"location",
                                     @"relevance", @"order_by",
                                     @"/common/topic", @"type",
                                     @"point", @"geometry_type",
                                     nil] JSONRepresentation];
            NSMutableDictionary*
            options = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                       geo_filter, @"geo_filter",
                       _preferredType, @"type",
                       nil];
            [self updateSearchOptions:options];
            
            NSLog(@"Search with geo_filter for %@ using %@", searchString, options);
            if ([searchString length]>5 && 
                ![searchString hasSuffix:@"*"])
                searchString = [NSString stringWithFormat:@"%@*", searchString];
            [self.currentRequest cancel];
            self.currentRequest = [[FreebaseSession session] search:searchString
                                                               name:@"geo_relevance" 
                                                           delegate:self 
                                                  didFinishSelector:@selector(searchFinished:)
                                                            options:options];
            
        } else {
            NSMutableDictionary*
            options = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                       distance, @"within",
                       @"relevance", @"order_by",
                       mql_output_string, @"mql_output",
                       _preferredType, @"type",
                       @"point", @"geometry_type",
                       nil];
            [self updateSearchOptions:options];
            NSLog(@"Searching geo-only");
            [self.currentRequest cancel];
            self.currentRequest = [[FreebaseSession session] geosearch:locationString
                                                                  name:@"suggest" 
                                                              delegate:self 
                                                     didFinishSelector:@selector(geoSearchFinished:)
                                                               options:options];
        }
        
        [mql_output_string release];
    }
}

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
#pragma unused(oldLocation)
    self.currentLocation = newLocation;
    NSLog(@"New location! %@", [newLocation description]);
    
    if (_awaitingLocationForSearch) {
        _awaitingLocationForSearch = NO;
        [self beginGeosearch];
    } else {
        // ok, we can simply stop at this point - 
        // it means we've gotten a second location marker, 
        // possibly more accurate than before - no sense in 
        // burning the battery. We don't set _awaitingLocation now,
        // because the one we've got is good enough
        NSLog(@"Got a second location, that's good enough for now.");
        [manager stopUpdatingLocation];
    }
}


-(void)updateSearchOptions:(NSMutableDictionary*)options
{
    if (self.strictType)
        [options setObject:@"1" forKey:@"strict"];
    
    if (self.mqlFilter)
        [options setObject:self.mqlFilter forKey:@"mql_filter"];
    
    if (self.mqlOutput)
        [options setObject:self.mqlOutput forKey:@"mql_output"];    
}

#pragma mark Search controller delegate methods
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
    NSLog(@"shouldReloadTableForSearchString: '%@'", searchString);    
    BOOL shouldReload = NO;
    
    // just to update the "Create.." string
    
    if ((_allowGeolocation && (controller.searchBar.selectedScopeButtonIndex != 0)) || // a geo search
        (searchString && [searchString length])) {
        [self maybeBeginSearch:controller.searchBar.text 
                    scopeIndex:controller.searchBar.selectedScopeButtonIndex];
        
        // reload to update the search string
        if (self.allowCreateNew)
            shouldReload = YES;
        
        return shouldReload;
    } else {        
        // no search string at all, easy to clear..
        self.currentResult = nil;
        return YES;
    }
}


- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption
{
    NSLog(@"shouldReloadTableforSearchScope: %d", searchOption);
    if (searchOption != 0) { // a geo search
        
        // this is silly - we want a geosearch,
        // but the table won't display until there is *some* string, so we make
        // it a space and then let shouldReloadTableForSearchString get called
        if (!controller.searchBar.text || ![controller.searchBar.text length]) {
            controller.searchBar.text = @" ";
            return NO;
        }
        
        [self maybeBeginSearch:controller.searchBar.text
                    scopeIndex:controller.searchBar.selectedScopeButtonIndex];
        
        return NO;
    } else {      
        NSString *searchString = [controller.searchBar.text 
                                  stringByTrimmingCharactersInSet:[NSCharacterSet 
                                                                   whitespaceCharacterSet]];
        if (![searchString length])
            controller.searchBar.text = @"";
        
        self.currentResult = nil;
        return YES;
    }
}

- (void)searchDisplayController:(UISearchDisplayController *)controller didShowSearchResultsTableView:(UITableView *)tableView
{
#pragma unused(controller)
    NSLog(@"didShowSeardchResultsTableView: %@", tableView);
    NSLog(@"   parent is %@", [tableView superview]);
    // make the activity indicator a sibling of the tableView, so that it can 
    // be displayed on top of it
    [[tableView superview] addSubview:_activityView];
    
    // weak ref
    _searchResultsTableView = tableView;
}


- (void)searchDisplayController:(UISearchDisplayController *)controller willUnloadSearchResultsTableView:(UITableView *)tableView
{
#pragma unused(controller)
#pragma unused(tableView)
    
    NSLog(@"Hid table...");
    // weak ref
    _searchResultsTableView = nil;
}

#pragma mark Table view methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
#pragma unused(tableView)
    
    // one for 'create new' and one for the results
    return 2;
}


// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
#pragma unused(tableView)
#pragma unused(section)
    if (section == 0) {        
        if (!_allowCreateNew)
            return 0;
        
        NSString *currentTitle = 
        [_searchString 
         stringByTrimmingCharactersInSet:
         [NSCharacterSet whitespaceAndNewlineCharacterSet]];
        
        if (currentTitle && [currentTitle length])        
            return 1;
        return 0;
    }
    
    NSInteger c = [self.currentResult count];
    return c;
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
#pragma unused(tableView)
    if (section == 0)
        return nil;

    if (_allowCreateNew && self.currentResult && [self.currentResult count])
        return @"Topics";

    return nil;
}

- (UITableViewCell*)createNewCell:(NSString*)title inTableView:(UITableView*)tableView
{
    static NSString *CellIdentifier = @"SuggestCreateNewCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                      reuseIdentifier:CellIdentifier];
    }
    cell.textLabel.text = [NSString stringWithFormat:@"Create \"%@\"...", title];
    
    return cell;
}

// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    if (_allowCreateNew && indexPath.section == 0) {
        NSString *currentTitle = 
        [[_searchString 
          stringByTrimmingCharactersInSet:
          [NSCharacterSet whitespaceAndNewlineCharacterSet]] capitalizedString];
        if (currentTitle && [currentTitle length])
            return [self createNewCell:currentTitle inTableView:tableView];
    }
    
    static NSString *CellIdentifier = @"SuggestCell";
    
    FBTableViewCell *cell = (FBTableViewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell.fbr)
        [cell.fbr cancel];
    
    if (cell == nil) {
        cell = [[[FBTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
        cell.session = [FreebaseSession session];
    }
    
    NSDictionary *item = [self.currentResult objectAtIndex:indexPath.row];
    
    cell.imageData = [item objectForKey:@"id"];
    
    NSString *name = [item objectForKey:@"name"];
    if (name && name != (NSString*)[NSNull null])
        cell.textLabel.text = name;
    else
        cell.textLabel.text = @"(unknown)";
    
    cell.detailTextLabel.text = [[item valueForKeyPath:@"type.name"] componentsJoinedByString:@", "];

    return cell;
}



- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
#pragma unused(tableView)
    if (indexPath.section == 0) {
        self.currentItem = [NSDictionary dictionaryWithObject:[_searchString capitalizedString]
                                                       forKey:@"name"];
        
    } else {
        NSDictionary* item = [self.currentResult objectAtIndex:indexPath.row];
        if (![item objectForKey:@"id"])
            // must have been an error!
            item = nil;
        self.currentItem = item;
    }
    [_suggestDelegate itemSelected:self.currentItem];
}

- (void)errorDidOccur:(id)result request:(id<FreebaseRequest>)request
{
#pragma unused(request)
    
    NSLog(@"oops...search error: %@", result);
    _activityView.hidden = YES;
    self.currentResult = [NSArray arrayWithObject:
                          [NSDictionary dictionaryWithObjectsAndKeys:
                           [NSString stringWithFormat:@"Error: %@", result], @"name",
                           nil]];
    [_searchResultsTableView reloadData];
}


- (void)dealloc {
    [_locationManager release];
    [_searchTimer invalidate];
    [_searchTimer release];
    [self.currentRequest cancel];
    self.currentRequest = nil;
    [super dealloc];
}


@end

